1 Setup

library(magrittr)
library(tidyverse)
library(glue)
library(arrow)
library(matric)
batch <- params$batch
futile.logger::flog.info(glue("Batch = {batch}"))
INFO [2021-04-17 12:15:27] Batch = 2020_11_04_CPJUMP1
experiment <- as.data.frame(params$experiment)
futile.logger::flog.info(glue("Experiment = {experiment}"))
INFO [2021-04-17 12:15:27] Experiment = Standard
INFO [2021-04-17 12:15:27] Experiment = Compound
INFO [2021-04-17 12:15:27] Experiment = U2OS
INFO [2021-04-17 12:15:27] Experiment = 48
data_level <- params$data_level
futile.logger::flog.info(glue("Data level = {data_level}"))
INFO [2021-04-17 12:15:27] Data level = normalized
if (params$normalization == "whole_plate") {
  norm_suffix <- ""
} else if (params$normalization == "negcon") {
  norm_suffix <- "_negcon"
}
sprintf("Normalization = %s. Using suffix = '%s'", params$normalization, norm_suffix)
[1] "Normalization = negcon. Using suffix = '_negcon'"
filename_prefix_profiles <- glue("{batch}_all_{data_level}{norm_suffix}")

2 Load

parquet_file_recoded <-
  glue("../collated/{batch}/{filename_prefix_profiles}_recoded_aggregated.parquet")

if (file.exists(parquet_file_recoded)) {
  parquet_file <- parquet_file_recoded
  
  futile.logger::flog.info(glue("Loading {parquet_file} ..."))
  
  stopifnot(file.exists(parquet_file))
  
  profiles <-
    read_parquet(parquet_file)
  
  variables <-
    str_subset(names(profiles), "Metadata_", negate = TRUE)
  
} else {
  
  parquet_file <-
    glue("../collated/{batch}/{filename_prefix_profiles}.parquet")
  
  futile.logger::flog.info(glue("Loading {parquet_file} ..."))
  
  stopifnot(file.exists(parquet_file))
  
  profiles <-
    read_parquet(parquet_file)
  
  if(!is.null(experiment)) {
    profiles <- profiles %>% inner_join(experiment)
  }
  
  variables <-
    str_subset(names(profiles), "Metadata_", negate = TRUE)
  
  metadata_cols <-
    str_subset(names(profiles), "Metadata_")
  
  variables <-
    params$variable_groups %>%
    unlist() %>%
    map(function(pattern)
      str_subset(variables, pattern = pattern)) %>%
    unlist()
  
  profiles <-
    profiles %>%
    select(all_of(c(metadata_cols, variables)))
  
  variables <-
    str_subset(names(profiles), "Metadata_", negate = TRUE)
  
  profiles <-
    profiles %>%
    group_by(across(params$strata_replicate)) %>%
    summarize(across(all_of(variables), median),
              .groups = "keep")
  
  attr(profiles, "variable_groups") <- params$variable_groups
  
  parquet_file <-
    glue("../collated/{batch}/{filename_prefix_profiles}_recoded_aggregated.parquet")
  
  futile.logger::flog.info(glue("Writing {parquet_file_recoded} ..."))
  
  profiles %>%
    write_parquet(parquet_file,
                  compression = "gzip",
                  compression_level = 9)
}
INFO [2021-04-17 12:15:28] Loading ../collated/2020_11_04_CPJUMP1/2020_11_04_CPJUMP1_all_normalized_negcon.parquet ...
Joining, by = c("Metadata_cell_line", "Metadata_timepoint", "Metadata_experiment_type", "Metadata_experiment_condition")
INFO [2021-04-17 12:16:21] Writing ../collated/2020_11_04_CPJUMP1/2020_11_04_CPJUMP1_all_normalized_negcon_recoded_aggregated.parquet ...

3 Transform

3.1 Functions

get_beta <- function(x = 5, y = 0.99) {
  -log(1 / y - 1) / x
}

abs_logistic <- function(x, beta = get_beta()) {
  2 / (1 + exp(-abs(x) * beta)) - 1
}

cytominer_variable_group_enrichment <-
  function(population,
           variables,
           variable_groups,
           sigmoid_function = abs_logistic,
           ...) {
    
    variables_group_lists <-
      variable_groups %>%
      unlist() %>%
      names() %>%
      set_names() %>%
      purrr::map(function(variable_group) {
        stringr::str_subset(variables, variable_group)
      }) 

    population_data_transformed <-
      variables_group_lists %>%
      map_dfc(function(variables_group_list) {
        population %>%
          dplyr::select(all_of(variables_group_list)) %>%
          dplyr::mutate(across(all_of(variables_group_list),
                               sigmoid_function,
                               ...)) %>%
          dplyr::ungroup() %>%
          dplyr::rowwise() %>%
          dplyr::transmute(n_above = round(sum(
            dplyr::c_across(everything()) /
              length(variables_group_list),
            na.rm = T
          ), 2)) %>%
          dplyr::pull(n_above)
      })
    
    population_metadata <-
      population %>%
      dplyr::select(-all_of(variables))
    
    enriched <-
      dplyr::bind_cols(population_metadata,
                       population_data_transformed)
    
    enriched
    
  }

3.2 Execute

profiles_enriched <-
  profiles %>%
  group_by(across(params$strata_replicate)) %>%
  summarize(across(all_of(variables), median), .groups = "keep") %>%
  group_by(across(params$nesting_level_0)) %>%
  summarise(
    cytominer_variable_group_enrichment(
      cur_data_all(),
      variables = variables,
      variable_groups = params$variable_groups
    ),
    .groups = "keep"
  ) %>%
  ungroup()

variables_enriched <-
  str_subset(names(profiles_enriched), "Metadata_", negate = TRUE)

4 Plot

profiles_enriched %>% 
  pivot_longer(-matches("Metadata"), 
               names_to = "feature_group")%>% 
  ggplot(aes(feature_group, value)) + 
  geom_boxplot() + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

profiles_enriched %>% 
  pivot_longer(-matches("Metadata"), 
               names_to = "feature_group") %>% 
  ggplot(aes(feature_group, value, group = Metadata_Well)) + 
  geom_line(alpha = .09) + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) + 
  facet_wrap(~Metadata_negcon_or_other, ncol = 1)

library(plotly)

df <- profiles_enriched

dimensions <- list(
  list(label = "Intensity_RNA",
       values = ~ Intensity_RNA),
  list(label = 'Intensity_DNA',
       values = ~ Intensity_DNA)
)

  
df <- df %>% select(all_of(variables_enriched))

dimensions <-
  variables_enriched %>%
  map(function(variable) {
    list(label = variable,
         values = df[[variable]])
  })

df %>%
  plot_ly(width = 1000, 
          height = 600,
          hoverinfo = "text") %>%
  add_trace(type = 'parcoords',
            dimensions = dimensions)
'parcoords' objects don't have these attributes: 'hoverinfo'
Valid attributes include:
'type', 'visible', 'name', 'uid', 'ids', 'customdata', 'meta', 'stream', 'transforms', 'uirevision', 'domain', 'labelangle', 'labelside', 'labelfont', 'tickfont', 'rangefont', 'dimensions', 'line', 'idssrc', 'customdatasrc', 'metasrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isGraticule', '_bbox'
'parcoords' objects don't have these attributes: 'hoverinfo'
Valid attributes include:
'type', 'visible', 'name', 'uid', 'ids', 'customdata', 'meta', 'stream', 'transforms', 'uirevision', 'domain', 'labelangle', 'labelside', 'labelfont', 'tickfont', 'rangefont', 'dimensions', 'line', 'idssrc', 'customdatasrc', 'metasrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isGraticule', '_bbox'

5 Save

attr(profiles, "variable_groups") <- params$variable_groups

parquet_file <-
  glue("../collated/{batch}/{filename_prefix_profiles}_enriched.parquet")

futile.logger::flog.info(glue("Writing {parquet_file_recoded} ..."))
INFO [2021-04-17 12:17:04] Writing ../collated/2020_11_04_CPJUMP1/2020_11_04_CPJUMP1_all_normalized_negcon_recoded_aggregated.parquet ...
profiles_enriched %>%
  write_parquet(parquet_file,
                compression = "gzip",
                compression_level = 9)
LS0tCnRpdGxlOiAiRmVhdHVyZSBhbmFseXNpcyIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIHRvY19kZXB0aDogMwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBsdW1lbgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAnMycKICAgIGRmX3ByaW50OiBwYWdlZApwYXJhbXM6CiAgYmF0Y2g6IDIwMjBfMTFfMDRfQ1BKVU1QMQogIGRhdGFfbGV2ZWw6IG5vcm1hbGl6ZWQKICBub3JtYWxpemF0aW9uOiBuZWdjb24KICBleHBlcmltZW50OiAKICAgIHZhbHVlOgogICAgICBNZXRhZGF0YV9leHBlcmltZW50X2NvbmRpdGlvbjogU3RhbmRhcmQKICAgICAgTWV0YWRhdGFfZXhwZXJpbWVudF90eXBlOiBDb21wb3VuZAogICAgICBNZXRhZGF0YV9jZWxsX2xpbmU6IFUyT1MKICAgICAgTWV0YWRhdGFfdGltZXBvaW50OiAiNDgiCiAgbmVzdGluZ19sZXZlbF8wOgogICAgdmFsdWU6CiAgICAgIC0gTWV0YWRhdGFfZXhwZXJpbWVudF9jb25kaXRpb24KICAgICAgLSBNZXRhZGF0YV9leHBlcmltZW50X3R5cGUKICAgICAgLSBNZXRhZGF0YV9jZWxsX2xpbmUKICAgICAgLSBNZXRhZGF0YV90aW1lcG9pbnQKICBzdHJhdGFfcmVwbGljYXRlOgogICAgdmFsdWU6CiAgICAgIC0gTWV0YWRhdGFfZXhwZXJpbWVudF9jb25kaXRpb24KICAgICAgLSBNZXRhZGF0YV9leHBlcmltZW50X3R5cGUKICAgICAgLSBNZXRhZGF0YV9jZWxsX2xpbmUKICAgICAgLSBNZXRhZGF0YV90aW1lcG9pbnQKICAgICAgLSBNZXRhZGF0YV9wbGF0ZV9tYXBfbmFtZQogICAgICAtIE1ldGFkYXRhX1dlbGwKICAgICAgLSBNZXRhZGF0YV9nZW5lcwogICAgICAtIE1ldGFkYXRhX3BlcnRfdHlwZQogICAgICAtIE1ldGFkYXRhX2NvbnRyb2xfdHlwZQogICAgICAtIE1ldGFkYXRhX1BsYXRlX01hcF9OYW1lCiAgICAgIC0gTWV0YWRhdGFfbmVnY29uX2NvbnRyb2xfdHlwZQogICAgICAtIE1ldGFkYXRhX3RhcmdldF9zZXF1ZW5jZQogICAgICAtIE1ldGFkYXRhX21nX3Blcl9tbAogICAgICAtIE1ldGFkYXRhX21tb2xlc19wZXJfbGl0ZXIKICAgICAgLSBNZXRhZGF0YV9zb2x2ZW50CiAgICAgIC0gTWV0YWRhdGFfdGFyZ2V0CiAgICAgIC0gTWV0YWRhdGFfcGVydF9pbmFtZQogICAgICAtIE1ldGFkYXRhX3B1YmNoZW1fY2lkCiAgICAgIC0gTWV0YWRhdGFfSW5DaElLZXkKICAgICAgLSBNZXRhZGF0YV9nZW5lCiAgICAgIC0gTWV0YWRhdGFfbmVnY29uX29yX290aGVyCiAgICAgIC0gTWV0YWRhdGFfbmVnY29uX2NvbnRyb2xfdHlwZV90cmltbWVkCiAgdmFyaWFibGVfZ3JvdXBzOgogICAgdmFsdWU6CiAgICAgIC0geEFyZWE6IF9BcmVhU2hhcGVfQXJlYSQKICAgICAgLSB4U2hhcGU6IEFyZWFTaGFwZQogICAgICAtIHhOZWlnaDogTmVpZ2hib3JzCiAgICAgIC0geENvcnI6IENvcnJlbGF0aW9uCiAgICAgIC0gVGV4X0FHUDogKChUZXh0dXJlfEdyYW51bGFyaXR5fFJhZGlhbERpc3RyaWJ1dGlvbilfLipfKEFHUCkpCiAgICAgIC0gVGV4X0ROQTogKChUZXh0dXJlfEdyYW51bGFyaXR5fFJhZGlhbERpc3RyaWJ1dGlvbilfLipfKEROQSkpCiAgICAgIC0gVGV4X0VSOiAoKFRleHR1cmV8R3JhbnVsYXJpdHl8UmFkaWFsRGlzdHJpYnV0aW9uKV8uKl8oRVIpKQogICAgICAtIFRleF9NaXRvOiAoKFRleHR1cmV8R3JhbnVsYXJpdHl8UmFkaWFsRGlzdHJpYnV0aW9uKV8uKl8oTWl0bykpCiAgICAgIC0gVGV4X1JOQTogKChUZXh0dXJlfEdyYW51bGFyaXR5fFJhZGlhbERpc3RyaWJ1dGlvbilfLipfKFJOQSkpCiAgICAgIC0gSW50X0FHUDogKChJbnRlbnNpdHkpXy4qXyhBR1ApKQogICAgICAtIEludF9ETkE6ICgoSW50ZW5zaXR5KV8uKl8oRE5BKSkKICAgICAgLSBJbnRfRVI6ICgoSW50ZW5zaXR5KV8uKl8oRVIpKQogICAgICAtIEludF9NaXRvOiAoKEludGVuc2l0eSlfLipfKE1pdG8pKQogICAgICAtIEludF9STkE6ICgoSW50ZW5zaXR5KV8uKl8oUk5BKSkKLS0tCgojIFNldHVwCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KG1hZ3JpdHRyKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnbHVlKQpsaWJyYXJ5KGFycm93KQpsaWJyYXJ5KG1hdHJpYykKYGBgCgoKYGBge3J9CmJhdGNoIDwtIHBhcmFtcyRiYXRjaApmdXRpbGUubG9nZ2VyOjpmbG9nLmluZm8oZ2x1ZSgiQmF0Y2ggPSB7YmF0Y2h9IikpCmBgYApgYGB7cn0KZXhwZXJpbWVudCA8LSBhcy5kYXRhLmZyYW1lKHBhcmFtcyRleHBlcmltZW50KQpmdXRpbGUubG9nZ2VyOjpmbG9nLmluZm8oZ2x1ZSgiRXhwZXJpbWVudCA9IHtleHBlcmltZW50fSIpKQpgYGAKCgpgYGB7cn0KZGF0YV9sZXZlbCA8LSBwYXJhbXMkZGF0YV9sZXZlbApmdXRpbGUubG9nZ2VyOjpmbG9nLmluZm8oZ2x1ZSgiRGF0YSBsZXZlbCA9IHtkYXRhX2xldmVsfSIpKQpgYGAKCgpgYGB7cn0KaWYgKHBhcmFtcyRub3JtYWxpemF0aW9uID09ICJ3aG9sZV9wbGF0ZSIpIHsKICBub3JtX3N1ZmZpeCA8LSAiIgp9IGVsc2UgaWYgKHBhcmFtcyRub3JtYWxpemF0aW9uID09ICJuZWdjb24iKSB7CiAgbm9ybV9zdWZmaXggPC0gIl9uZWdjb24iCn0Kc3ByaW50ZigiTm9ybWFsaXphdGlvbiA9ICVzLiBVc2luZyBzdWZmaXggPSAnJXMnIiwgcGFyYW1zJG5vcm1hbGl6YXRpb24sIG5vcm1fc3VmZml4KQpgYGAKCgpgYGB7cn0KZmlsZW5hbWVfcHJlZml4X3Byb2ZpbGVzIDwtIGdsdWUoIntiYXRjaH1fYWxsX3tkYXRhX2xldmVsfXtub3JtX3N1ZmZpeH0iKQpgYGAKCiMgTG9hZAoKYGBge3J9CnBhcnF1ZXRfZmlsZV9yZWNvZGVkIDwtCiAgZ2x1ZSgiLi4vY29sbGF0ZWQve2JhdGNofS97ZmlsZW5hbWVfcHJlZml4X3Byb2ZpbGVzfV9yZWNvZGVkX2FnZ3JlZ2F0ZWQucGFycXVldCIpCgppZiAoZmlsZS5leGlzdHMocGFycXVldF9maWxlX3JlY29kZWQpKSB7CiAgcGFycXVldF9maWxlIDwtIHBhcnF1ZXRfZmlsZV9yZWNvZGVkCiAgCiAgZnV0aWxlLmxvZ2dlcjo6ZmxvZy5pbmZvKGdsdWUoIkxvYWRpbmcge3BhcnF1ZXRfZmlsZX0gLi4uIikpCiAgCiAgc3RvcGlmbm90KGZpbGUuZXhpc3RzKHBhcnF1ZXRfZmlsZSkpCiAgCiAgcHJvZmlsZXMgPC0KICAgIHJlYWRfcGFycXVldChwYXJxdWV0X2ZpbGUpCiAgCiAgdmFyaWFibGVzIDwtCiAgICBzdHJfc3Vic2V0KG5hbWVzKHByb2ZpbGVzKSwgIk1ldGFkYXRhXyIsIG5lZ2F0ZSA9IFRSVUUpCiAgCn0gZWxzZSB7CiAgCiAgcGFycXVldF9maWxlIDwtCiAgICBnbHVlKCIuLi9jb2xsYXRlZC97YmF0Y2h9L3tmaWxlbmFtZV9wcmVmaXhfcHJvZmlsZXN9LnBhcnF1ZXQiKQogIAogIGZ1dGlsZS5sb2dnZXI6OmZsb2cuaW5mbyhnbHVlKCJMb2FkaW5nIHtwYXJxdWV0X2ZpbGV9IC4uLiIpKQogIAogIHN0b3BpZm5vdChmaWxlLmV4aXN0cyhwYXJxdWV0X2ZpbGUpKQogIAogIHByb2ZpbGVzIDwtCiAgICByZWFkX3BhcnF1ZXQocGFycXVldF9maWxlKQogIAogIGlmKCFpcy5udWxsKGV4cGVyaW1lbnQpKSB7CiAgICBwcm9maWxlcyA8LSBwcm9maWxlcyAlPiUgaW5uZXJfam9pbihleHBlcmltZW50KQogIH0KICAKICB2YXJpYWJsZXMgPC0KICAgIHN0cl9zdWJzZXQobmFtZXMocHJvZmlsZXMpLCAiTWV0YWRhdGFfIiwgbmVnYXRlID0gVFJVRSkKICAKICBtZXRhZGF0YV9jb2xzIDwtCiAgICBzdHJfc3Vic2V0KG5hbWVzKHByb2ZpbGVzKSwgIk1ldGFkYXRhXyIpCiAgCiAgdmFyaWFibGVzIDwtCiAgICBwYXJhbXMkdmFyaWFibGVfZ3JvdXBzICU+JQogICAgdW5saXN0KCkgJT4lCiAgICBtYXAoZnVuY3Rpb24ocGF0dGVybikKICAgICAgc3RyX3N1YnNldCh2YXJpYWJsZXMsIHBhdHRlcm4gPSBwYXR0ZXJuKSkgJT4lCiAgICB1bmxpc3QoKQogIAogIHByb2ZpbGVzIDwtCiAgICBwcm9maWxlcyAlPiUKICAgIHNlbGVjdChhbGxfb2YoYyhtZXRhZGF0YV9jb2xzLCB2YXJpYWJsZXMpKSkKICAKICB2YXJpYWJsZXMgPC0KICAgIHN0cl9zdWJzZXQobmFtZXMocHJvZmlsZXMpLCAiTWV0YWRhdGFfIiwgbmVnYXRlID0gVFJVRSkKICAKICBwcm9maWxlcyA8LQogICAgcHJvZmlsZXMgJT4lCiAgICBncm91cF9ieShhY3Jvc3MocGFyYW1zJHN0cmF0YV9yZXBsaWNhdGUpKSAlPiUKICAgIHN1bW1hcml6ZShhY3Jvc3MoYWxsX29mKHZhcmlhYmxlcyksIG1lZGlhbiksCiAgICAgICAgICAgICAgLmdyb3VwcyA9ICJrZWVwIikKICAKICBhdHRyKHByb2ZpbGVzLCAidmFyaWFibGVfZ3JvdXBzIikgPC0gcGFyYW1zJHZhcmlhYmxlX2dyb3VwcwogIAogIHBhcnF1ZXRfZmlsZSA8LQogICAgZ2x1ZSgiLi4vY29sbGF0ZWQve2JhdGNofS97ZmlsZW5hbWVfcHJlZml4X3Byb2ZpbGVzfV9yZWNvZGVkX2FnZ3JlZ2F0ZWQucGFycXVldCIpCiAgCiAgZnV0aWxlLmxvZ2dlcjo6ZmxvZy5pbmZvKGdsdWUoIldyaXRpbmcge3BhcnF1ZXRfZmlsZV9yZWNvZGVkfSAuLi4iKSkKICAKICBwcm9maWxlcyAlPiUKICAgIHdyaXRlX3BhcnF1ZXQocGFycXVldF9maWxlLAogICAgICAgICAgICAgICAgICBjb21wcmVzc2lvbiA9ICJnemlwIiwKICAgICAgICAgICAgICAgICAgY29tcHJlc3Npb25fbGV2ZWwgPSA5KQp9CmBgYAoKIyBUcmFuc2Zvcm0KCiMjIEZ1bmN0aW9ucwoKYGBge3J9CmdldF9iZXRhIDwtIGZ1bmN0aW9uKHggPSA1LCB5ID0gMC45OSkgewogIC1sb2coMSAvIHkgLSAxKSAvIHgKfQoKYWJzX2xvZ2lzdGljIDwtIGZ1bmN0aW9uKHgsIGJldGEgPSBnZXRfYmV0YSgpKSB7CiAgMiAvICgxICsgZXhwKC1hYnMoeCkgKiBiZXRhKSkgLSAxCn0KCmN5dG9taW5lcl92YXJpYWJsZV9ncm91cF9lbnJpY2htZW50IDwtCiAgZnVuY3Rpb24ocG9wdWxhdGlvbiwKICAgICAgICAgICB2YXJpYWJsZXMsCiAgICAgICAgICAgdmFyaWFibGVfZ3JvdXBzLAogICAgICAgICAgIHNpZ21vaWRfZnVuY3Rpb24gPSBhYnNfbG9naXN0aWMsCiAgICAgICAgICAgLi4uKSB7CiAgICAKICAgIHZhcmlhYmxlc19ncm91cF9saXN0cyA8LQogICAgICB2YXJpYWJsZV9ncm91cHMgJT4lCiAgICAgIHVubGlzdCgpICU+JQogICAgICBuYW1lcygpICU+JQogICAgICBzZXRfbmFtZXMoKSAlPiUKICAgICAgcHVycnI6Om1hcChmdW5jdGlvbih2YXJpYWJsZV9ncm91cCkgewogICAgICAgIHN0cmluZ3I6OnN0cl9zdWJzZXQodmFyaWFibGVzLCB2YXJpYWJsZV9ncm91cCkKICAgICAgfSkgCgogICAgcG9wdWxhdGlvbl9kYXRhX3RyYW5zZm9ybWVkIDwtCiAgICAgIHZhcmlhYmxlc19ncm91cF9saXN0cyAlPiUKICAgICAgbWFwX2RmYyhmdW5jdGlvbih2YXJpYWJsZXNfZ3JvdXBfbGlzdCkgewogICAgICAgIHBvcHVsYXRpb24gJT4lCiAgICAgICAgICBkcGx5cjo6c2VsZWN0KGFsbF9vZih2YXJpYWJsZXNfZ3JvdXBfbGlzdCkpICU+JQogICAgICAgICAgZHBseXI6Om11dGF0ZShhY3Jvc3MoYWxsX29mKHZhcmlhYmxlc19ncm91cF9saXN0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpZ21vaWRfZnVuY3Rpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4pKSAlPiUKICAgICAgICAgIGRwbHlyOjp1bmdyb3VwKCkgJT4lCiAgICAgICAgICBkcGx5cjo6cm93d2lzZSgpICU+JQogICAgICAgICAgZHBseXI6OnRyYW5zbXV0ZShuX2Fib3ZlID0gcm91bmQoc3VtKAogICAgICAgICAgICBkcGx5cjo6Y19hY3Jvc3MoZXZlcnl0aGluZygpKSAvCiAgICAgICAgICAgICAgbGVuZ3RoKHZhcmlhYmxlc19ncm91cF9saXN0KSwKICAgICAgICAgICAgbmEucm0gPSBUCiAgICAgICAgICApLCAyKSkgJT4lCiAgICAgICAgICBkcGx5cjo6cHVsbChuX2Fib3ZlKQogICAgICB9KQogICAgCiAgICBwb3B1bGF0aW9uX21ldGFkYXRhIDwtCiAgICAgIHBvcHVsYXRpb24gJT4lCiAgICAgIGRwbHlyOjpzZWxlY3QoLWFsbF9vZih2YXJpYWJsZXMpKQogICAgCiAgICBlbnJpY2hlZCA8LQogICAgICBkcGx5cjo6YmluZF9jb2xzKHBvcHVsYXRpb25fbWV0YWRhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgcG9wdWxhdGlvbl9kYXRhX3RyYW5zZm9ybWVkKQogICAgCiAgICBlbnJpY2hlZAogICAgCiAgfQpgYGAKCiMjIEV4ZWN1dGUKCmBgYHtyfQpwcm9maWxlc19lbnJpY2hlZCA8LQogIHByb2ZpbGVzICU+JQogIGdyb3VwX2J5KGFjcm9zcyhwYXJhbXMkc3RyYXRhX3JlcGxpY2F0ZSkpICU+JQogIHN1bW1hcml6ZShhY3Jvc3MoYWxsX29mKHZhcmlhYmxlcyksIG1lZGlhbiksIC5ncm91cHMgPSAia2VlcCIpICU+JQogIGdyb3VwX2J5KGFjcm9zcyhwYXJhbXMkbmVzdGluZ19sZXZlbF8wKSkgJT4lCiAgc3VtbWFyaXNlKAogICAgY3l0b21pbmVyX3ZhcmlhYmxlX2dyb3VwX2VucmljaG1lbnQoCiAgICAgIGN1cl9kYXRhX2FsbCgpLAogICAgICB2YXJpYWJsZXMgPSB2YXJpYWJsZXMsCiAgICAgIHZhcmlhYmxlX2dyb3VwcyA9IHBhcmFtcyR2YXJpYWJsZV9ncm91cHMKICAgICksCiAgICAuZ3JvdXBzID0gImtlZXAiCiAgKSAlPiUKICB1bmdyb3VwKCkKCnZhcmlhYmxlc19lbnJpY2hlZCA8LQogIHN0cl9zdWJzZXQobmFtZXMocHJvZmlsZXNfZW5yaWNoZWQpLCAiTWV0YWRhdGFfIiwgbmVnYXRlID0gVFJVRSkKYGBgCgojIFBsb3QKCmBgYHtyfQpwcm9maWxlc19lbnJpY2hlZCAlPiUgCiAgcGl2b3RfbG9uZ2VyKC1tYXRjaGVzKCJNZXRhZGF0YSIpLCAKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiZmVhdHVyZV9ncm91cCIpJT4lIAogIGdncGxvdChhZXMoZmVhdHVyZV9ncm91cCwgdmFsdWUpKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQpgYGAKYGBge3J9CnByb2ZpbGVzX2VucmljaGVkICU+JSAKICBwaXZvdF9sb25nZXIoLW1hdGNoZXMoIk1ldGFkYXRhIiksIAogICAgICAgICAgICAgICBuYW1lc190byA9ICJmZWF0dXJlX2dyb3VwIikgJT4lIAogIGdncGxvdChhZXMoZmVhdHVyZV9ncm91cCwgdmFsdWUsIGdyb3VwID0gTWV0YWRhdGFfV2VsbCkpICsgCiAgZ2VvbV9saW5lKGFscGhhID0gLjA5KSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkgKyAKICBmYWNldF93cmFwKH5NZXRhZGF0YV9uZWdjb25fb3Jfb3RoZXIsIG5jb2wgPSAxKQpgYGAKYGBge3J9CmxpYnJhcnkocGxvdGx5KQoKZGYgPC0gcHJvZmlsZXNfZW5yaWNoZWQKCmRpbWVuc2lvbnMgPC0gbGlzdCgKICBsaXN0KGxhYmVsID0gIkludGVuc2l0eV9STkEiLAogICAgICAgdmFsdWVzID0gfiBJbnRlbnNpdHlfUk5BKSwKICBsaXN0KGxhYmVsID0gJ0ludGVuc2l0eV9ETkEnLAogICAgICAgdmFsdWVzID0gfiBJbnRlbnNpdHlfRE5BKQopCgogIApkZiA8LSBkZiAlPiUgc2VsZWN0KGFsbF9vZih2YXJpYWJsZXNfZW5yaWNoZWQpKQoKZGltZW5zaW9ucyA8LQogIHZhcmlhYmxlc19lbnJpY2hlZCAlPiUKICBtYXAoZnVuY3Rpb24odmFyaWFibGUpIHsKICAgIGxpc3QobGFiZWwgPSB2YXJpYWJsZSwKICAgICAgICAgdmFsdWVzID0gZGZbW3ZhcmlhYmxlXV0pCiAgfSkKCmRmICU+JQogIHBsb3RfbHkod2lkdGggPSAxMDAwLCAKICAgICAgICAgIGhlaWdodCA9IDYwMCwKICAgICAgICAgIGhvdmVyaW5mbyA9ICJ0ZXh0IikgJT4lCiAgYWRkX3RyYWNlKHR5cGUgPSAncGFyY29vcmRzJywKICAgICAgICAgICAgZGltZW5zaW9ucyA9IGRpbWVuc2lvbnMpCmBgYAoKIyBTYXZlCgpgYGB7cn0KYXR0cihwcm9maWxlcywgInZhcmlhYmxlX2dyb3VwcyIpIDwtIHBhcmFtcyR2YXJpYWJsZV9ncm91cHMKCnBhcnF1ZXRfZmlsZSA8LQogIGdsdWUoIi4uL2NvbGxhdGVkL3tiYXRjaH0ve2ZpbGVuYW1lX3ByZWZpeF9wcm9maWxlc31fZW5yaWNoZWQucGFycXVldCIpCgpmdXRpbGUubG9nZ2VyOjpmbG9nLmluZm8oZ2x1ZSgiV3JpdGluZyB7cGFycXVldF9maWxlX3JlY29kZWR9IC4uLiIpKQoKcHJvZmlsZXNfZW5yaWNoZWQgJT4lCiAgd3JpdGVfcGFycXVldChwYXJxdWV0X2ZpbGUsCiAgICAgICAgICAgICAgICBjb21wcmVzc2lvbiA9ICJnemlwIiwKICAgICAgICAgICAgICAgIGNvbXByZXNzaW9uX2xldmVsID0gOSkKYGBgCgoK